<?php
namespace app\api\controller;

use app\api\model\OrderModel;
use app\api\model\PaymentConfigModel;
use think\Controller;

/**
 * 微信支付回调接口
 */
class PayCallback extends Controller
{
    /**
     * 统一下单请求时的微信回调
     * 注意定期去清理支付日志，日志只记录了报错和被攻击的情形，所以不多
     * @return string
     */
    public function index() {
        // 支付日志路径
        $payment_log = ROOT_PATH . 'runtime' . DS . 'payment' . DS . date('Y-m-d', time()) . '.txt';
        // 支付错误日志路径
        $payment_error_log = ROOT_PATH . 'runtime' . DS . 'payment' . DS . 'error' . DS . date('Y-m-d', time()) . '.txt';
        // 获取回调反馈的数据
        $xmlData = file_get_contents("php://input");
        if (!empty($xmlData)) {
            $data = $this->getxml($xmlData);
        } else {
            return true;
        }
        // 返回判断,交易成功，处理
        if ($data['RETURN_CODE'] == 'SUCCESS' && $data['RESULT_CODE'] == 'SUCCESS') {
            try {
                // 判断签名和金额
                $sign = $this->sign($data);
                $orderM = new OrderModel();
                $order = $orderM->where(['trade_no' => $data['OUT_TRADE_NO']])->find();
                if (empty($order) || $order['money'] != ($data['TOTAL_FEE']/100) || $sign != $data['SIGN']) { // 被伪造
                    // 写入日志即可
                    $str = '回调时间：' . date('Y-m-d H:i:s', time()) . '  回调的数据（伪造请求嫌疑）：' . json_encode($data) . PHP_EOL;
                    file_put_contents($payment_log, $str . PHP_EOL, FILE_APPEND);
                    return true;
                }

                // 支付成功的处理逻辑
                // 判断状态是否已经修改
                if ($order['status'] == OrderModel::STATUS_OK) { // 已经修改，通知微信，不再回调
                    return sprintf("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
                }
                // 修改订单状态
                $up_data['status'] = OrderModel::STATUS_OK;
                $up_data['transaction_id'] = $data['TRANSACTION_ID'];
                $res = $orderM->allowField(true)->save($up_data, ['id' => $order['id']]);
                if (!$res) { // 更新失败，继续微信回调
                    // 写入错误日志
                    $str = '回调时间：' . date('Y-m-d H:i:s', time()) . '  回调的数据(更新失败)：' . json_encode($data) . PHP_EOL;
                    file_put_contents($payment_error_log, $str . PHP_EOL, FILE_APPEND);
                    return true;
                }
            } catch (\Exception $e) {
                $str = '回调时间：' . date('Y-m-d H:i:s', time()) . '  回调的数据(程序报错)：' . json_encode($data) . PHP_EOL . '错误信息：' . $e->getMessage() . PHP_EOL;
                file_put_contents($payment_error_log, $str . PHP_EOL, FILE_APPEND);
                return true;
            }
        }
        // 交易失败或者更新成功，通知微信停止回调
        return sprintf("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
    }


    /**
     * 返回xml数据
     * @param $data
     * @return string
     */
    public function xml($data)
    {
        ksort($data);
        // 拼接数据
        $data_xml = "<xml>";
        foreach ($data as $k=>$v) {
            if (is_numeric($v)) {
                $data_xml .= "<" .$k. ">" .$v. "</" .$k. ">";
            } else {
                $data_xml .= "<" .$k. "><![CDATA[" .$v. "]]></" .$k. ">";
            }
        }
        $data_xml .= "</xml>";
        return $data_xml;
    }

    /**
     * 获取xml
     * @param $xml
     * @return array
     */
    private function getxml($xml)
    {
        $p = xml_parser_create();
        xml_parse_into_struct($p, $xml, $vals, $index);
        xml_parser_free($p);
        $data = [];
        foreach ($index as $key=>$value) {
            if ($key == 'xml' || $key == 'XML') continue;
            $tag = $vals[$value[0]]['tag'];
            $value = $vals[$value[0]]['value'];
            $data[$tag] = $value;
        }
        return $data;
    }

    public function sign($data)
    {
        // 获取支付配置的key
        $key = PaymentConfigModel::getKeyByWhere(['mch_id' => $data['MCH_ID']]);

        // 字典序排列
        ksort($data);
        $buff = '';
        foreach ($data as $k=>$v) {
            if ($k != 'SIGN' && $v != '' && !is_array($v) && $k != 'sign') {
                $buff .= strtolower($k). '=' .$v. '&';
            }
        }
        $buff = trim($buff, '&');
        // 拼接key
        $string = $buff. '&key=' . $key;
        // md5加密
        $string = md5($string);
        // 转为大写
        $sign = strtoupper($string);
        return $sign;
    }
}